Skip to content

Fix contours() auto-levels silently dropping all contours with +/-inf#2801

Merged
brendancol merged 2 commits into
mainfrom
issue-2797
Jun 1, 2026
Merged

Fix contours() auto-levels silently dropping all contours with +/-inf#2801
brendancol merged 2 commits into
mainfrom
issue-2797

Conversation

@brendancol
Copy link
Copy Markdown
Contributor

Summary

Fixes #2797. When contours() generates levels automatically (levels=None), a +inf or -inf cell mixed with finite terrain made nanmin/nanmax return infinity. The np.isnan-only guard let it through, and np.linspace(vmin, inf, ...) then produced non-finite levels, so the function returned no contours at all. Every contour for the finite terrain disappeared, with no error.

  • Reduce the auto-level range over finite values only (treat +/-inf like NaN) in the numpy, cupy, and dask branches.
  • Change the guard from np.isnan to not np.isfinite so an all-non-finite raster still returns an empty result cleanly.
  • Silence the cosmetic "All-NaN slice" warning the all-non-finite case would otherwise raise.

Backend coverage

numpy, cupy, dask+numpy, dask+cupy. The fix sits in the shared auto-level path before backend dispatch, so all four behave the same.

Test plan

  • New TestAutoLevelsInf class: auto-levels ignore +/-inf, levels match the finite-only range, all-inf returns empty, explicit levels unaffected.
  • Cross-backend tests for numpy/dask/cupy/dask+cupy auto-levels with inf (cupy variants skip without a GPU).
  • Full test_contour.py suite passes (46 passed locally).

@github-actions github-actions Bot added the performance PR touches performance-sensitive code label Jun 1, 2026
Copy link
Copy Markdown
Contributor Author

@brendancol brendancol left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Review: Fix contours() auto-levels silently dropping all contours with +/-inf

Blockers (must fix before merge)

None.

Suggestions (should fix, not blocking)

None.

Nits (optional improvements)

  • xrspatial/contour.py:649 -- the numpy branch builds a full-size copy with np.where(np.isfinite(agg.values), agg.values, np.nan). Since the data is already in memory, the cost is one transient array of the same size that the old np.nanmin(agg.values) did not allocate. Fine to leave as-is: it keeps the three branches reading the same way, and the extra peak memory only matters for very large numpy rasters. Flagging so it is a conscious call rather than an oversight.
  • xrspatial/tests/test_contour.py -- the file already has a TestInfHandling class from #2704 that exercises the kernel/explicit-level path. The new tests live in TestAutoLevelsInf to dodge the name clash, which is the right move. The two classes hit different code (auto-level range vs. per-quad interpolation), so the similar names are not a problem.

What looks good

  • The fix is in the shared pre-dispatch path, so one change covers numpy, cupy, dask+numpy, and dask+cupy identically.
  • The dask branch stays lazy. da.where plus da.nanmin/da.nanmax under one dask.compute, same two-reduction shape as before, nothing newly materialized.
  • Swapping the guard from np.isnan to not np.isfinite keeps the old all-NaN behavior (returns []) and now covers all-inf too.
  • The "All-NaN slice" RuntimeWarning from the all-non-finite case is suppressed in a tight scope around just that block, not globally.
  • Tests cover the reported bug, a check that levels match the finite-only range, the all-inf empty case, explicit levels staying unaffected, and all four backends (cupy variants skip without a GPU).

Checklist

  • NaN and inf handling is correct
  • All implemented backends produce consistent results (numpy + dask verified locally; cupy paths skip without a GPU)
  • Edge cases covered by tests (mixed inf, all-inf, explicit levels)
  • Dask path stays lazy, no premature materialization
  • Docstrings unchanged (no API change); contours already in reference docs
  • No README matrix change needed (no new function, no backend change)

@brendancol brendancol merged commit 58a44c3 into main Jun 1, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance PR touches performance-sensitive code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

contours(): auto-levels silently drop all contours when the raster contains +/-inf

1 participant